﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Text;
using Dycox.Drawing;
using Dycox.Linq;

namespace Dycox.Reporting
{
    public class RenderingContext
    {
        Stack<ContainerState> _containers;
        PropertyDescriptorCollection _properties;

        internal RenderingContext(Report report, Graphics graphics)
        {
            _containers = new Stack<ContainerState>();
            _graphics = graphics;
            _report = report;
        }

        private Report _report;

        public Report Report
        {
            get { return _report; }
        }

        private Graphics _graphics;

        public Graphics Graphics
        {
            get { return _graphics; }
        }

        private object _dataObject;

        public object DataObject
        {
            get { return _dataObject; }
        }

        private PagingInfo _pagingInfo;

        private ContainerState BeginRenderContainer(ContainerElement container)
        {
            ContainerState state = new ContainerState(container, this);
            _containers.Push(state);

            _graphics.SetClip(new RectangleF(container.AbsoluteLocation, state.Bounds.Size), CombineMode.Intersect);
            PointF offset = container.Location;
            Paddings paddings = container.Paddings;

            _graphics.TranslateTransform(offset.X + paddings.Left, offset.Y + paddings.Right);

            return state;
        }

        public ContainerState CurrentContainer
        {
            get
            {
                if (_containers.Count > 0) return _containers.Peek();
                else return null;
            }
        }

        private void EndRenderContainer()
        {
            var state = _containers.Pop();

            state.Restore();
        }

        public void Render(object dataObject, PagingInfo pagingInfo)
        {
            if (_containers.Count > 0)
                throw new InvalidOperationException("Cannot start rendering while previous operation has not completed.");

            _pagingInfo = pagingInfo;

            if (dataObject != null)
            {
                _properties = TypeDescriptor.GetProperties(dataObject);
                _dataObject = dataObject;
            }
            else
            {
                _properties = null;
                _dataObject = null;
            }

            GraphicsState gs = _graphics.Save();
            InitializeGraphics();
            RenderContainer(_report.Body);
            _graphics.Restore(gs);
        }

        private SizeF _bodySize;

        public SizeF BodySize
        {
            get { return _bodySize; }
        }


        private void RenderContainer(ContainerElement container)
        {
            BeginRenderContainer(container);
            if (container == _report.Body)
                _bodySize = CurrentContainer.Bounds.Size;

            container.Render(this);
            foreach (ReportElement elt in container.Elements)
            {
                if (elt is ContainerElement)
                    RenderContainer((ContainerElement)elt);
                else
                    elt.Render(this);
            }

            EndRenderContainer();
        }

        private void InitializeGraphics()
        {
            var unit = _report.PageSettings.Unit;

            _graphics.PageUnit = GraphicsUnit.Millimeter;
            if (unit == ReportPageUnit.Centimeter)
                _graphics.ScaleTransform(10, 10);
            _graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
            _graphics.SmoothingMode = SmoothingMode.HighQuality;
        }

        public string EvalString(string expression)
        {
            if (string.IsNullOrEmpty(expression))
                return null;

            char id = expression[0];

            if (id == '@' || id == '#') // || id == '$')
            {

                object value;
                string expr, format;
                int p;

                expr = expression.Substring(1);
                p = expr.IndexOf(';');
                if (p > 0 && p < expr.Length - 1)
                {
                    format = expr.Substring(p + 1);
                    expr = expr.Substring(0, p);
                }
                else
                    format = null;

                switch (id)
                {
                    case '@':
                        try
                        {
                            value = _dataObject == null ? null : Dycox.Utils.DataBinderUtil.Eval(_dataObject, expr);
                        }
                        catch
                        {
                            value = "ERR: " + expression;
                        }
                        break;
                    case '#':
                        value = _report.GetVariable(expr);
                        break;
                    case '$':
                        if (_properties != null)
                        {
                            var pd = _properties[expr];
                            if (pd != null)
                                value = pd.DisplayName;
                            else
                                value = expr;
                        }
                        else
                            value = expr;
                        break;
                    default:
                        throw new InvalidOperationException("NO WAY.");
                }

                if (format != null)
                {
                    value = string.Format(format, value);
                }
                else if (!(value is string))
                {
                    PropertyDescriptor pd;
                    if (_properties != null)
                        pd = _properties[expr];
                    else
                        pd = null;

                    value = Dycox.Utils.Formatting.ToString(value, pd);

                }

                return (string)value;
            }
            else if (id == '=')
            {
                string text;
                try
                {
                    text = Dycox.Utils.DataBinderUtil.EvalTemplate(expression.Substring(1), _dataObject);
                }
                catch
                {
                    text = "ERR: " + expression;
                }

                return text;
            }
            else
                return expression;
        }

    }

    public class ContainerState
    {
        public ContainerState(ContainerElement element, RenderingContext context)
        {
            _element = element;
            _context = context;
            _graphicsState = context.Graphics.Save();
            
            _bounds = element.GetBounds(_context);
        }

        private GraphicsState _graphicsState;

        private ContainerElement _element;

        public ContainerElement Element
        {
            get { return _element; }
        }

        private RenderingContext _context;

        public RenderingContext Context
        {
            get { return _context; }
        }

        private RectangleF _bounds;

        public RectangleF Bounds
        {
            get { return _bounds; }
        }

        internal void Restore()
        {
            _context.Graphics.Restore(_graphicsState);
        }
    }
}
